package com.rhaggarty.blackjack.cardroom;

import java.math.BigDecimal;

import org.apache.commons.lang.NotImplementedException;

import com.rhaggarty.blackjack.game.GameResult;
import com.rhaggarty.blackjack.game.GameStatus;
import com.rhaggarty.blackjack.game.Hand;
import com.rhaggarty.blackjack.game.Player;
import com.rhaggarty.data.type.game.Card;
import com.rhaggarty.gaming.cardroom.Shoe;
import com.rhaggarty.gaming.cardroom.ShoeFinishedException;
import com.rhaggarty.gaming.cardroom.Shuffler;
import com.rhaggarty.gaming.cardroom.SimpleRandomShuffler;

/**
 * A single-seat implementation of {@link Dealer}.  
 * 
 * This dealer maintains a shoe of size={@link #NUM_DECKS} complete decks.
 * 
 * @author Ryan Haggarty (ryanmh@gmail.com)
 *
 * TODO(rhaggarty): Extract DECK_SIZE to injectable property/conf.
 */
public final class SinglePlayerDealer implements Dealer {

    private static final int DECK_SIZE = 6;
    
    private static final BigDecimal EVEN_RETURN = new BigDecimal("2");
    
    private static final BigDecimal BLACKJACK_RETURN = new BigDecimal("2.5");
    
    private final Seat dealerSeat = Seat.newDealerSeat();
    
    private Seat seat = Seat.emptySeat();
    
    private final Shoe shoe;
    
    private final Shuffler shuffler = new SimpleRandomShuffler();
    
    public SinglePlayerDealer() {
        shoe = new Shoe(DECK_SIZE);
        shuffler.shuffle(shoe);
    }

    public GameResult bet(final BigDecimal amount) {
        initNewRound();
        
        seat.addWager(amount);
        seat.dealCard(nextCard());
        seat.dealCard(nextCard());
        dealerSeat.dealCard(nextCard());

        return seat.getHand().hasBlackjack() ? stand() : GameResult.PENDING;
    }

    public GameResult buyInsurance() {
        throw new NotImplementedException();
    }

    public GameResult doubleDown() {
        validateSeat();
        
        seat.addWager(seat.getAmountInPlay());
        final GameResult interimResult = hit();
        
        if (GameResult.PENDING.equals(interimResult)) {
            return stand();
        } else {
            return interimResult;
        }
    }

    public Hand getDealerHand() {
        return dealerSeat.getHand();
    }

    public GameResult hit() {
        validateSeat();
        seat.dealCard(nextCard());

        if (seat.getHand().getHigh() == 0) {
            return resolveSeat(GameStatus.BUST, BigDecimal.ZERO);
        } else if (seat.getHand().getHigh() == Hand.BLACKJACK) {
            return stand();
        } else { 
            return GameResult.PENDING;
        }
    }

    public GameResult split() {
        throw new NotImplementedException();
    }

    public GameResult stand() {
        validateSeat();
        
        dealerSeat.dealCard(nextCard());
        if (seat.getHand().hasBlackjack()) {
        
            if (dealerSeat.getHand().hasBlackjack()) {
                return resolveSeat(GameStatus.PUSH, seat.getAmountInPlay());
            }
            return resolveSeat(GameStatus.BLACKJACK, seat.getAmountInPlay().multiply(BLACKJACK_RETURN));  
        
        } else {
            while (dealerSeat.getHand().getHigh() != 0 && dealerSeat.getHand().getHigh() <= 16) {
                // Check for soft 16 stand...
                // TODO(rhaggarty): extract
                if (dealerSeat.getHand().getHigh() == 16 && seat.getHand().getHigh() < 16) {
                    break;
                } 
                    
                dealerSeat.dealCard(nextCard());
            }
            
            //TODO(rhaggarty): extract
            if (dealerSeat.getHand().getAllValues().size() > 0) {
                if (dealerSeat.getHand().getHigh() > seat.getHand().getHigh()) {
                    return resolveSeat(GameStatus.LOSS, BigDecimal.ZERO);
                } else if (dealerSeat.getHand().getHigh() == seat.getHand().getHigh()) {
                    return resolveSeat(GameStatus.PUSH, seat.getAmountInPlay());
                }
            }
            
            return resolveSeat(GameStatus.WIN, seat.getAmountInPlay().multiply(EVEN_RETURN));
        }
    }
    
    public Seat sitPlayer(final Player player) {
        seat.sitPlayer(player);
        return seat;
    }
    
    
    private void initNewRound() {
        seat.clearCards();
        seat.setStatus(GameStatus.PENDING);
        dealerSeat.clearCards();
    }
    
    private Card nextCard() {
        try {
            return shoe.nextCard();
        } catch (final ShoeFinishedException ex) {
            shuffler.shuffle(shoe);
            shoe.resetShoe();
            try {
                return shoe.nextCard();
            } catch (final Exception ex2) {
                // launder
                throw new RuntimeException(ex2);
            }
        } 
    }
    
    private GameResult resolveSeat(GameStatus status, BigDecimal winAmount) {
        seat.getPlayer().alterStack(winAmount.negate());
        seat.setStatus(status);
        
        return new GameResult(status, winAmount);
    }
    
    private void validateSeat() {
        if (!GameStatus.PENDING.equals(seat.getStatus())) {
            throw new IllegalStateException("Action not allowed.  Please start new round");
        }
    }
}
